package net.penguinmenac3.math;

/**
 * Represents a simple matrix.
 * 
 * Gives some calculation features.
 * Required for gausian algorithm.
 * 
 * @author Michael
 */
public class Matrix {
	protected MathObject[][] m;

	/**
	 * The first dimension are rows and the second are columns.
	 * 
	 * @param m
	 */
	public Matrix(MathObject m[][]) {
		if (m == null) {
			throw new NullPointerException();
		}
		if (m.length == 0) {
			throw new IllegalArgumentException("Matrix must not have 0 rows");
		}
		int columns = m[0].length;
		for (int i = 0; i < m.length; i++) {
			if (m[i].length != columns) {
				throw new IllegalArgumentException(
						"The amount of columns must be constant per row");
			}
		}
		this.m = m;
	}

	/**
	 * Mutiply a row by a given multiplier.
	 * 
	 * @param index
	 * @param multiplier
	 */
	public void multiplyRow(int index, MathObject multiplier) {
		if (index >= m.length || index < 0) {
			throw new IndexOutOfBoundsException();
		}
		for (int i = 0; i < m[index].length; i++) {
			m[index][i] = m[index][i].multiply(multiplier);
		}
	}

	/**
	 * Divide a row by a given multiplier.
	 * 
	 * @param index
	 * @param multiplier
	 */
	public void divideRow(int index, MathObject multiplier) {
		if (index >= m.length || index < 0) {
			throw new IndexOutOfBoundsException();
		}
		for (int i = 0; i < m[index].length; i++) {
			m[index][i] = m[index][i].divide(multiplier);
		}
	}

	/**
	 * Subtract second from first row. Hint: Second row remains unchanged.
	 * 
	 * @param index
	 * @param multiplier
	 */
	public void subtractRows(int index, int index2) {
		if (index >= m.length || index2 >= m.length || index < 0 || index2 < 0) {
			throw new IndexOutOfBoundsException();
		}
		for (int i = 0; i < m[index].length; i++) {
			m[index][i] = m[index][i].subtract(m[index2][i]);
		}
	}

	/**
	 * Add second to first row. Hint: Second row remains unchanged.
	 * 
	 * @param index
	 * @param multiplier
	 */
	public void addRows(int index, int index2) {
		if (index >= m.length || index2 >= m.length || index < 0 || index2 < 0) {
			throw new IndexOutOfBoundsException();
		}
		for (int i = 0; i < m[index].length; i++) {
			m[index][i] = m[index][i].add(m[index2][i]);
		}
	}

	/**
	 * Return the row at the given index.
	 * 
	 * @param index
	 * @return
	 */
	public MathObject[] getRow(int index) {
		if (index >= m.length || index < 0) {
			throw new IndexOutOfBoundsException();
		}
		return m[index];
	}

	/**
	 * Set a row to certain values.
	 * 
	 * @param index
	 * @param row
	 */
	public void setRow(int index, MathObject[] row) {
		if (index >= m.length || index < 0) {
			throw new IndexOutOfBoundsException();
		}
		m[index] = row;
	}

	/**
	 * Return the number of rows.
	 * 
	 * @return
	 */
	public int getRowCount() {
		return m.length;
	}

	/**
	 * Return the number of columns.
	 * 
	 * @return
	 */
	public int getColumnCount() {
		return m[0].length;
	}

	/**
	 * Return a formated String.
	 * 
	 * @return
	 */
	public String toString() {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < m.length; i++) {
			for (int j = 0; j < m[i].length - 1; j++) {
				sb.append(m[i][j]);
				sb.append(", ");
			}
			sb.append(m[i][m[i].length - 1]);
			sb.append("\n");
		}
		return sb.toString();
	}

	/**
	 * Sort a matrix by first non null index.
	 */
	public void sort() {
		for (int i = 0; i < m.length; i++) {
			int max = Integer.MAX_VALUE;
			int maxID = 0;
			for (int j = i; j < m.length; j++) {
				int compare = new Vector(getRow(j))
						.getFirstIndexOfNonNullElement();
				if (compare < max) {
					maxID = j;
					max = compare;
				}
			}
			if (max != Integer.MAX_VALUE) {
				swap(i, maxID);
			}
		}
	}

	private void swap(int index1, int index2) {
		MathObject[] temp = m[index1];
		m[index1] = m[index2];
		m[index2] = temp;
	}
}
